package org.elucid.chess;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.log4j.Logger;
import org.elucid.chess.model.Board;
import org.elucid.chess.model.Game;
import org.elucid.chess.model.Move;
import org.elucid.chess.model.Piece;
import org.elucid.chess.model.Player;
import org.elucid.chess.model.Square;
import org.elucid.chess.piece.Bishop;
import org.elucid.chess.piece.King;
import org.elucid.chess.piece.Knight;
import org.elucid.chess.piece.Pawn;
import org.elucid.chess.piece.Queen;
import org.elucid.chess.piece.Rook;
import org.elucid.collections.BiMap;

/**
 * Provide default implementations for Game interface
 * 
 * @author george
 *
 */
public abstract class AbstractGame implements Game {

	private static Logger log = Logger.getLogger(AbstractGame.class);
	private Board board;

	private BiMap<Piece, Square> pieceSquares = new BiMap<Piece, Square>();

	protected Player whitePlayer;
	protected Player blackPlayer;
	protected Player currentPlayer;
	protected boolean gameOver = false;
	
	protected List<Move> moveList = new LinkedList<Move>();
	private Map<Square, Piece> threatenedSquares = new HashMap<Square,Piece>();
	private Map<Square, Piece> threateningSquares = new HashMap<Square,Piece>();
	private List<Piece> capturedPieces = new LinkedList<Piece>();

	/**
	 * 
	 * @param board
	 * @param whitePlayer
	 * @param blackPlayer
	 */
	public AbstractGame() {
		super();

		setBoard(new Board());

		pieceSquares.put(new Pawn(true), board.getSquareAt(1, 0));
		pieceSquares.put(new Pawn(true), board.getSquareAt(1, 1));
		pieceSquares.put(new Pawn(true), board.getSquareAt(1, 2));
		pieceSquares.put(new Pawn(true), board.getSquareAt(1, 3));
		pieceSquares.put(new Pawn(true), board.getSquareAt(1, 4));
		pieceSquares.put(new Pawn(true), board.getSquareAt(1, 5));
		pieceSquares.put(new Pawn(true), board.getSquareAt(1, 6));
		pieceSquares.put(new Pawn(true), board.getSquareAt(1, 7));

		pieceSquares.put(new Rook(true), board.getSquareAt(0, 0));
		pieceSquares.put(new Knight(true), board.getSquareAt(0, 1));
		pieceSquares.put(new Bishop(true), board.getSquareAt(0, 2));
		pieceSquares.put(new King(true), board.getSquareAt(0, 3));
		pieceSquares.put(new Queen(true), board.getSquareAt(0, 4));
		pieceSquares.put(new Bishop(true), board.getSquareAt(0, 5));
		pieceSquares.put(new Knight(true), board.getSquareAt(0, 6));
		pieceSquares.put(new Rook(true), board.getSquareAt(0, 7));

		pieceSquares.put(new Pawn(false), board.getSquareAt(6, 0));
		pieceSquares.put(new Pawn(false), board.getSquareAt(6, 1));
		pieceSquares.put(new Pawn(false), board.getSquareAt(6, 2));
		pieceSquares.put(new Pawn(false), board.getSquareAt(6, 3));
		pieceSquares.put(new Pawn(false), board.getSquareAt(6, 4));
		pieceSquares.put(new Pawn(false), board.getSquareAt(6, 5));
		pieceSquares.put(new Pawn(false), board.getSquareAt(6, 6));
		pieceSquares.put(new Pawn(false), board.getSquareAt(6, 7));

		pieceSquares.put(new Rook(false), board.getSquareAt(7, 0));
		pieceSquares.put(new Knight(false), board.getSquareAt(7, 1));
		pieceSquares.put(new Bishop(false), board.getSquareAt(7, 2));
		pieceSquares.put(new King(false), board.getSquareAt(7, 3));
		pieceSquares.put(new Queen(false), board.getSquareAt(7, 4));
		pieceSquares.put(new Bishop(false), board.getSquareAt(7, 5));
		pieceSquares.put(new Knight(false), board.getSquareAt(7, 6));
		pieceSquares.put(new Rook(false), board.getSquareAt(7, 7));
		
	}
	
	/**
	 * Evaluate all legal moves
	 * 
	 * @return
	 */
	public List<Move> listLegalMoves()
	{
		
		List<Move> moves = new ArrayList<Move>();
		// find all my pieces
		for( Entry<Piece, Square> pieceSquare: this.getPieceSquares().entrySet() )
		{
			// is this my piece?
			if( pieceSquare.getKey().isWhite() == currentPlayer.isWhite()  )
			{
				// consider all the possible moves for this piece
				moves.addAll( pieceSquare.getKey().listLegalMoves(this, false) );
			}
		}
		
		// remove any moves which, after they're made, leave the King in Check
		List<Move> illegalMoves = new ArrayList<Move>();
		Iterator<Move> it = moves.iterator();
		Player playerToMove = this.getCurrentPlayer();
		while(it.hasNext())
		{
			Move move = it.next();
			this.doMove(move);
			// is my King in check?
			King king = (King) this.getPlayerPiece(playerToMove, King.class);
			Square squareForKing = this.getPieceSquares().get(king);
			if( this.getThreateningSquares().keySet().contains(squareForKing) )
			{
				illegalMoves.add(move);
			}
			this.undoMove(move);
		}
		
		// now remove all illegal moves (after, to avoid CoModEx)
		for( Move illegalMove : illegalMoves )
		{
			moves.remove(illegalMove);
		}
		
		return moves;
	}
		

	/**
	 * 
	 * @param fromSquare
	 * @param toSquare
	 * @return
	 */
	public boolean doMove(Move move) {

		Piece srcPiece = pieceSquares.getKey(move.getFromSquare());
		Piece dstPiece = pieceSquares.getKey(move.getToSquare());
		
		// store piece info (needed for Undo)
		move.setSrcPiece(srcPiece);
		move.setDstPiece(dstPiece);

		if( dstPiece!=null )
		{
			// capture piece in destination square
			pieceSquares.remove(dstPiece);
			capturedPieces.add(dstPiece);
		}
		
		pieceSquares.remove(srcPiece);
		pieceSquares.put(srcPiece, move.getToSquare());

		moveList.add(move);
		srcPiece.getMoveHistory().add(move.getFromSquare());
		currentPlayer = currentPlayer == whitePlayer ? blackPlayer : whitePlayer;
		
		calculateThreats();

		return true;
	}
	
	public void undoMove(Move move) {
		Piece dstPiece = move.getDstPiece();
		Piece srcPiece = move.getSrcPiece();
		
		pieceSquares.remove(srcPiece);
		pieceSquares.put(srcPiece, move.getFromSquare());
		if( dstPiece != null )
		{
			// uncapture piece
			capturedPieces.remove(dstPiece);
			pieceSquares.put(dstPiece, move.getToSquare());
		}
		
		moveList.remove(move);
		srcPiece.getMoveHistory().remove(move.getFromSquare());
		currentPlayer = currentPlayer == whitePlayer ? blackPlayer : whitePlayer;
		
		calculateThreats();
		
	}
	
	

	@Override
	public Piece getPlayerPiece(Player player, Class<? extends Piece> pieceClass) {
		Piece king = null;
		// find all my pieces
		for( Entry<Piece, Square> pieceSquare: this.getPieceSquares().entrySet() )
		{
			if( pieceSquare.getKey().getClass().equals( pieceClass ) )
			{
				// is this my piece?
				if( pieceSquare.getKey().isWhite() == player.isWhite()  )
				{
					king = (King) pieceSquare.getKey();
				}
			}
		}
		return king;
	}

	protected void calculateThreats() {
		this.threatenedSquares = new HashMap<Square, Piece>();
		this.threateningSquares = new HashMap<Square, Piece>();
		
		// iterate over pieces on board
		for( Entry<Piece, Square> pieceSquare : pieceSquares.entrySet() )
		{
			
			// add the squares it threatens to the list
			List<Move> moves = pieceSquare.getKey().listLegalMoves(this, true);
			for(Move move : moves)
			{
				if( move.isPotentialCapture() )
				{
					Piece piece = pieceSquare.getKey();
					if( piece.isWhite() !=  getCurrentPlayer().isWhite() )
					{
						threatenedSquares.put(move.getToSquare(), piece);
					}
					else
						threateningSquares.put(move.getToSquare(), piece);
				}
			}
		}
		
	}

	@Override
	public Move waitForPlayerMove() throws Exception {
		Move move = currentPlayer.chooseMove();
		return move;
	}


	public void setBoard(Board board) {
		this.board = board;
	}

	public Board getBoard() {
		return board;
	}

	public BiMap<Piece, Square> getPieceSquares() {
		return pieceSquares;
	}

	public boolean isGameOver() {
		return gameOver;
	}

	public Player getWhitePlayer() {
		return whitePlayer;
	}

	public Player getCurrentPlayer() {
		return currentPlayer;
	}

	public Player getBlackPlayer() {
		return blackPlayer;
	}

	public List<Move> getMoveList() {
		return moveList;
	}

	public Map<Square, Piece> getThreatenedSquares() {
		return threatenedSquares;
	}

	public void setThreateningSquares(Map<Square, Piece> threateningSquares) {
		this.threateningSquares = threateningSquares;
	}

	public Map<Square, Piece> getThreateningSquares() {
		return threateningSquares;
	}



}
